package de.playmee;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Stack;

import de.playmee.handler.MusicHandler;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.widget.ImageView;

public class ImageManager {
	
	private final String TAG = "ImageManager";
	
	private final int dummyResId;

	private HashMap<String, SoftReference<Bitmap>> imageMap = new HashMap<String, SoftReference<Bitmap>>();

	private MusicHandler mHandler;
	private File cacheDir;
	private ImageQueue imageQueue = new ImageQueue();
	private Thread imageLoaderThread = new Thread(new ImageQueueManager());

	public ImageManager(Context context, MusicHandler mHandler, int dummyResId) {
		this.mHandler = mHandler;
		this.dummyResId = dummyResId;
		
		// make background thread low priority, to avoid affecting UI performance
		imageLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);

		// find the folder to save cached images
		/*
		// TODO manifest: permission to write into the external storage
		String sdState = android.os.Environment.getExternalStorageState();
		if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
			File sdDir = android.os.Environment.getExternalStorageDirectory();
			cacheDir = new File(sdDir, "PlaymeeCache/AlbumArts");
		} else
		*/
		cacheDir = context.getCacheDir();

		if (!cacheDir.exists())
			cacheDir.mkdirs();
	}
	
	/**
	 * @return size of the cache folder
	 */
	public long getCacheDirSize() {
		Log.i(TAG, "Retrieving size of the cache folder...");
		long size = 0;
		
		if (cacheDir != null && cacheDir.isDirectory()) {
			File[] files = cacheDir.listFiles();
			for (File file : files) {
				size += file.length();
			}
		}
		
		Log.i(TAG, "Size of the cache folder: " + size + " bytes");
		return size;
	}
	
	/**
	 * deletes all files in the cache folder
	 * 
	 * @return true if deleting of all files was successful, false otherwise
	 */
	public boolean clearCacheDir() {
		Log.i(TAG, "Deleting all files in the cache folder...");
		if (cacheDir == null || !cacheDir.isDirectory())
			return false;
		
		boolean result = true;
		File[] files = cacheDir.listFiles();
		for (File file : files) {
			boolean deleated = file.delete();
			if (!deleated)
				result = false;
		}
		
		Log.i(TAG, result ? "Done deleting all files." : "Error deleting all files.");
		return result;
	}

	/**
	 * displays the album art of the track in the given image view
	 * 
	 * @param track
	 * @param imageView
	 */
	public void displayImage(Track track, ImageView imageView) {
		String key = track.getAlbumArtKey();
		if (imageMap.containsKey(key)) {
			Bitmap bmp = imageMap.get(key).get();
			if (bmp != null) {
				imageView.setImageBitmap(imageMap.get(key).get());
				Log.i(TAG, "Image loaded from cache.");
			} else {
				// bitmap was recycled, so reload it from the cache
				Log.i(TAG, "Reloading recycled image...");
				imageMap.remove(key);
				queueImage(track, imageView);
			}
		}
		else {
			Log.i(TAG, "Loading image...");
			queueImage(track, imageView);
		}
	}

	/**
	 * puts a track to the loading list and starts
	 * the image-loader-thread if necessary
	 * 
	 * @param track
	 * @param imageView
	 */
	private void queueImage(Track track, ImageView imageView) {
		imageView.setTag(track.getAlbumArtKey());
		
		// ImageView might have been used for other images, so clear the queue
		// of old tasks before starting
		imageQueue.clean(imageView);
		ImageRef p = new ImageRef(track, imageView);

		synchronized (imageQueue.imageRefs) {
			imageQueue.imageRefs.push(p);
			imageQueue.imageRefs.notifyAll();
		}

		// start thread if it's not started yet
		if (imageLoaderThread.getState() == Thread.State.NEW)
			imageLoaderThread.start();
	}

	/**
	 * loads the album art of a track from the cache
	 * or from the track itself
	 * 
	 * @param track
	 * @return album art
	 */
	private Bitmap getBitmap(Track track) {
		String filename = track.getAlbumArtKey();
		File f = new File(cacheDir, filename);

		// bitmap is in the cache
		Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());
		if (bitmap != null)
			return bitmap;

		// bitmap is not in the cache, so load it
		try {
			bitmap = mHandler.loadAlbumArt(track);
			writeFile(bitmap, f); // save it to the cache
			return bitmap;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * writes a bitmap to the cache
	 * 
	 * @param bmp
	 * @param f
	 */
	private void writeFile(Bitmap bmp, File f) {
		FileOutputStream out = null;

		try {
			out = new FileOutputStream(f);
			bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (out != null)
					out.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}
	
	/**
	 * class that represents a track and its corresponding image view
	 */
	private class ImageRef {
		public Track track;
		public ImageView imageView;

		public ImageRef(Track t, ImageView i) {
			track = t;
			imageView = i;
		}
	}

	/**
	 * class that stores a list of images to load
	 */
	private class ImageQueue {
		private Stack<ImageRef> imageRefs = new Stack<ImageRef>();

		// removes all instances of this ImageView
		public void clean(ImageView view) {

			for (int i = 0; i < imageRefs.size();) {
				if (imageRefs.get(i).imageView == view)
					imageRefs.remove(i);
				else
					++i;
			}
		}
	}

	/**
	 * executable command that loads all images and displays them in the UI thread
	 */
	private class ImageQueueManager implements Runnable {
		@Override
		public void run() {
			try {
				while (true) {
					// thread waits until there are images in the queue to be retrieved
					if (imageQueue.imageRefs.size() == 0) {
						synchronized (imageQueue.imageRefs) {
							imageQueue.imageRefs.wait();
						}
					}

					// there are images to be loaded
					if (imageQueue.imageRefs.size() != 0) {
						ImageRef imageToLoad;

						synchronized (imageQueue.imageRefs) {
							imageToLoad = imageQueue.imageRefs.pop();
						}

						Bitmap bmp = getBitmap(imageToLoad.track);
						String key = imageToLoad.track.getAlbumArtKey();
						imageMap.put(key, new SoftReference<Bitmap>(bmp));
						Object tag = imageToLoad.imageView.getTag();

						// make sure we have the right view - thread safety defender
						if (tag != null && ((String) tag).equals(key)) {
							BitmapDisplayer bmpDisplayer = new BitmapDisplayer(
									bmp, imageToLoad.imageView);

							Activity a = (Activity) imageToLoad.imageView.getContext();
							a.runOnUiThread(bmpDisplayer);
						}
					}

					if (Thread.interrupted())
						break;
				}
			} catch (InterruptedException e) {
			}
		}
	}

	/**
	 * executable command that is used to display a bitmap in the UI thread
	 */
	private class BitmapDisplayer implements Runnable {
		Bitmap bitmap;
		ImageView imageView;

		public BitmapDisplayer(Bitmap b, ImageView i) {
			bitmap = b;
			imageView = i;
		}

		public void run() {
			if (bitmap != null)
				imageView.setImageBitmap(bitmap);
			else
				imageView.setImageResource(dummyResId);
		}
	}
}